Skip to content

Admin Portal - Fix GetTwoFactorProviders wrong exception type handling on Organization and User - #8234

Merged
JaredSnider-Bitwarden merged 4 commits into
mainfrom
admin-portal/organizations-2fa-deserialization-error-catch-fix
Aug 31, 2026
Merged

Admin Portal - Fix GetTwoFactorProviders wrong exception type handling on Organization and User#8234
JaredSnider-Bitwarden merged 4 commits into
mainfrom
admin-portal/organizations-2fa-deserialization-error-catch-fix

Conversation

@JaredSnider-Bitwarden

@JaredSnider-Bitwarden JaredSnider-Bitwarden commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-42460

📔 Objective

Organization.GetTwoFactorProviders and User.GetTwoFactorProviders both catch JsonException intending to return null on malformed JSON (per their doc comments). But Organization.cs and User.cs each import System.Text.Json, so the catch binds to System.Text.Json.JsonException — while JsonHelpers.LegacyDeserialize uses Newtonsoft under the hood and throws Newtonsoft.Json.JsonException (or its JsonReaderException / JsonSerializationException subclasses), an unrelated type. The catch never matched, so any row with an invalid TwoFactorProviders value took down the admin console Organizations view (via Admin/Controllers/OrganizationsController) and would take down the Users view the same way (via TwoFactorIsEnabledQuery fanned out by Admin/Controllers/UsersController).

Observed locally: integration-test rows seeded via AutoFixture left "TwoFactorProviders" + Guid.NewGuid() in the column (AutoFixture's default string generator prefixes with the property name), which is not valid JSON.

Fix: catch Newtonsoft.Json.JsonException (base of both reader and serialization exceptions) on both entities so the methods degrade to null as intended, drop the now-unused System.Text.Json using, and add matching theories on OrganizationTests and UserTests covering pure garbage, the AutoFixture pattern, malformed JSON, and a schema mismatch.

📸 Screenshots

Before:

Before

After:

After

GetTwoFactorProviders catches JsonException intending to return null when
TwoFactorProviders contains invalid JSON (as its doc comment promises).
Organization.cs imports System.Text.Json, so that catch binds to
System.Text.Json.JsonException — but JsonHelpers.LegacyDeserialize uses
Newtonsoft.Json under the hood and throws Newtonsoft.Json.JsonException
(or its JsonReaderException / JsonSerializationException subclasses),
which is an unrelated type. The catch never matched and the exception
bubbled up through the admin console Organizations view any time a row
had a malformed TwoFactorProviders value, taking down the whole page.

Trigger observed locally: integration-test orgs seeded via AutoFixture
had "TwoFactorProviders" + Guid.NewGuid() left in the column (the default
AutoFixture string generator uses the property name as a prefix), which
is not valid JSON.

Fix: catch Newtonsoft.Json.JsonException (base of both reader and
serialization exceptions) so the method degrades to null as intended.
Drop the now-unused System.Text.Json using. Add a theory covering pure
garbage, the AutoFixture pattern, malformed JSON, and a schema mismatch.
@JaredSnider-Bitwarden
JaredSnider-Bitwarden marked this pull request as ready for review August 19, 2026 20:18
@JaredSnider-Bitwarden
JaredSnider-Bitwarden requested a review from a team as a code owner August 19, 2026 20:18
@JaredSnider-Bitwarden JaredSnider-Bitwarden added t:bugfix Change Type - Bugfix ai-review Request a Claude code review labels Aug 19, 2026
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

The core fix is correct: JsonHelpers.LegacyDeserialize delegates to Newtonsoft.Json.JsonConvert.DeserializeObject, whose JsonReaderException and JsonSerializationException both derive from Newtonsoft.Json.JsonException, so the new catch clauses on User.GetTwoFactorProviders and Organization.GetTwoFactorProviders now actually match and honor the documented "return null on invalid JSON" contract. Both entities are now consistent with the already-correct OrganizationUserUserDetails.GetTwoFactorProviders, and these three are the only LegacyDeserialize call sites in src/, so no other instance of this mismatch remains; neither entity retains any System.Text.Json usage or attribute after the using removals, so the removals are safe. The new ILogger<TwoFactorIsEnabledQuery> constructor parameter resolves through the existing AddScoped<ITwoFactorIsEnabledQuery, TwoFactorIsEnabledQuery> registration, no call site constructs the query manually, every test in TwoFactorIsEnabledQueryTests uses SutProvider, and the warn log records only the user id — never the raw column — so the zero-knowledge and logging rules hold. Dropping static from GetEnabledTwoFactorProviders keeps the users.ToDictionary(u => u.Id, GetEnabledTwoFactorProviders) method group valid, and the null branch preserves the prior providers == null || providers.Count == 0 behavior exactly, adding only the log.

Code Review Details

No findings.

Previously raised and closed by the author, not reopened here: the silent degradation on the Organization path (no equivalent to the user-side warn log), and the login-path fail-open on User where providers == null is treated as "no providers". The latter was the pre-existing contract at the GetEnabledTwoFactorProviders layer, the column is not user-writable, and corrupt rows are now observable via the new log — deferring to those decisions.

Comment thread src/Core/AdminConsole/Entities/Organization.cs
@JaredSnider-Bitwarden
JaredSnider-Bitwarden marked this pull request as draft August 19, 2026 20:24
User.cs has the identical defect Organization.cs did: it imports
System.Text.Json, so its catch (JsonException) binds to
System.Text.Json.JsonException — but JsonHelpers.LegacyDeserialize
uses Newtonsoft.Json and throws Newtonsoft.Json.JsonException. The
catch never matched, so a User row with malformed TwoFactorProviders
would bubble up through any caller (e.g. TwoFactorIsEnabledQuery,
fanned out by Admin/Controllers/UsersController), taking down the
admin console Users view the same way orgs did.

Fix mirrors the Organization commit: catch Newtonsoft.Json.JsonException,
drop the now-unused System.Text.Json using, add the same four-case
invalid-JSON theory to UserTests.
@JaredSnider-Bitwarden JaredSnider-Bitwarden changed the title Admin Portal - Fix Organization.GetTwoFactorProviders swallowing wrong exception type Admin Portal - Fix GetTwoFactorProviders swallowing wrong exception type on Organization and User Aug 19, 2026
Comment thread src/Core/Entities/User.cs
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.37%. Comparing base (cfd3ee5) to head (eafede4).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...torAuth/Implementations/TwoFactorIsEnabledQuery.cs 53.84% 5 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8234      +/-   ##
==========================================
+ Coverage   63.69%   69.37%   +5.67%     
==========================================
  Files        2470     2470              
  Lines      105798   105809      +11     
  Branches     9562     9564       +2     
==========================================
+ Hits        67389    73404    +6015     
+ Misses      36091    29959    -6132     
- Partials     2318     2446     +128     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Now that Newtonsoft parse failures degrade to null instead of bubbling
up as a 500, corruption of the TwoFactorProviders column is silent on
the login and admin Users paths. Warn-level log inside
TwoFactorIsEnabledQuery.GetEnabledTwoFactorProviders records the user
id (never the raw column, which carries 2FA secrets and metadata) when
providers is null but the column is non-empty — distinguishing a
parse failure from a normal "no 2FA configured" user.
@JaredSnider-Bitwarden
JaredSnider-Bitwarden marked this pull request as ready for review August 19, 2026 21:02
@JaredSnider-Bitwarden
JaredSnider-Bitwarden requested a review from a team as a code owner August 19, 2026 21:02
@JaredSnider-Bitwarden
JaredSnider-Bitwarden requested review from enmande and ike-kottlowski and removed request for enmande August 19, 2026 21:02
@JaredSnider-Bitwarden JaredSnider-Bitwarden changed the title Admin Portal - Fix GetTwoFactorProviders swallowing wrong exception type on Organization and User Admin Portal - Fix GetTwoFactorProviders wrong exception type handling on Organization and User Aug 19, 2026
Comment thread src/Core/AdminConsole/Entities/Organization.cs
@JaredSnider-Bitwarden
JaredSnider-Bitwarden enabled auto-merge (squash) August 31, 2026 19:27
@JaredSnider-Bitwarden
JaredSnider-Bitwarden merged commit 9a87169 into main Aug 31, 2026
45 of 46 checks passed
@JaredSnider-Bitwarden
JaredSnider-Bitwarden deleted the admin-portal/organizations-2fa-deserialization-error-catch-fix branch August 31, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review t:bugfix Change Type - Bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants